
----------------------------------------------------------------
--- Copyright：  迷你玩-海马
--- Description：定义所有模块可通用的函数
----------------------------------------------------------------


local assert     = assert
local require    = require
local bit        = bit
local coroutine  = coroutine
local debug      = debug
local io         = io
local pairs      = pairs
local ipairs     = ipairs
local math       = math
local os         = os
local print      = print
local pcall      = pcall
local xpcall     = xpcall
local rawget     = rawget
local rawset     = rawset
local select     = select
local string     = string
local table      = table
local tonumber   = tonumber
local tostring	 = tostring
local error      = error
local type       = type
local next       = next
local unpack     = unpack
local package    = package
local setmetatable = setmetatable
local getmetatable = getmetatable
local ngx = ngx


require "resty.core"
require "resty.core.shdict"


local loadstring = loadstring
local ngx_shared = ngx.shared;

local math_floor = math.floor


local table_insert  = table.insert
local string_format = string.format
local table_concat  = table.concat
local table_sort    = table.sort
local string_sub    = string.sub
local string_byte   = string.byte
local string_find   = string.find
local string_gsub   = string.gsub

local ngx_thread_spawn = ngx.thread.spawn
local ngx_thread_wait = ngx.thread.wait

local log         = require("modules.commhm.log");
local cjson_safe  = require("cjson.safe");
--local shell = require("modules.commhm.resty.shell")


cjson_safe.encode_sparse_array(true);

local FOO = {
	_VERSION   = '0.12.1',
	time_locks = {},

	__INT32__  = 2147483648,
	__UINT32__ = 4294967296,

	exec_seq = 0,        --exec互斥使用
	exec_async_seq = 0,  --exec_async异步互斥使用

	work_id  =  ngx.worker.id(),
};



--获得请求的url
FOO.get_full_url = function()
	local url_ = ngx.var.scheme .. "://" .. ngx.var.server_addr .. ":" .. ngx.var.server_port .. ngx.var.request_uri
	return url_
end


--------------------------------------------s2和s3加密--------
--服务器控制的加密，可以随时替换
FOO.getS2 = function( uin_ )
	local time_ = FOO.now();
	local ret = ngx.md5( uin_ .. "s2_907_miniw_0320_" .. time_ );
	--ret = string.sub( ret, 1, 8 );
	log.debug( log.fast() and "get_s2=" .. ret .. ", t=" .. time_  );
	return ret .. "_" .. time_;
end;


--服务器控制的加密，可以随时替换
FOO.getS3 = function( uin_ )
	local time_ = FOO.now();
	local ret = ngx.md5( uin_ .. "s3_1006_miniw_0320_" .. time_ );
	--ret = string.sub( ret, 1, 16 );
	log.debug( log.fast() and "get_s3=" .. ret .. ", s2t=" .. time_  );
	return ret .. "_" .. time_;
end;


FOO.safe_loadstring = function( txt, no_check_safe )
	--安全的把一个string转换成lua的table

	local ret = {};

	local config_ = 'do local _local_var_ =\n' .. txt ..  '\nreturn	_local_var_; end';
	--log.debug( "-----------------------------------------" );
	--log.debug( config_ );
	--log.debug( "-----------------------------------------" );

	local need_check_function = false;  --需要检测function

	if  no_check_safe == true then
		--不用检查括号安全
	else
		if  string.find( txt, '%(' ) or  string.find( txt, '%)' )  then
			need_check_function = true
		end
	end


	local func = loadstring( config_ );
	log.debug( log.fast() and "safe load type=" .. type(func) );

	if func and type(func) == 'function' then
		ret = func();
		if ret and type(ret) == 'table' then
			--log.debug( "table====================================" );
			--log.debug( table.tostring(ret) );
			--log.debug( "table====================================" );
			--FOO.var_dump( ret );

			if  need_check_function then
				local has_ = FOO.var_dump_has_func( ret )
				if  has_ then
					log.error( "find '(' or ')' and functions in config ================ ERROR config " )
					return {}
				else
					log.debug( log.fast() and "NORMAL: find '(' or ')', but no functions in config." )
				end
			end

		else
			log.error("table data error.");
			return {};
		end
	end


	return ret or {};
end;



---http的按行打印，打印日志时候才用
FOO.debug_br = function( str_ )
	if  type(str_) == 'string' then
		local t_ = FOO.split( str_, '\n' )
		if  t_ then
			log.debug( table.concat( t_, '<br>\n' ) )
		end
	else
		log.debug( FOO.to_str( str_ ) )
	end
end


-- 打印异常trace_back 一般是给pcall xpcall使用
FOO.trace_back = function( errmsg )
	log.error( "--------trace_back errmsg=" .. FOO.to_str( errmsg ) );
	log.error( debug.traceback() );
end


-- 安全加载一个文件
FOO.safe_loadTable4File = function( filename, no_check_safe )
	local file = io.open( filename, "r");
	if  not file then
		log.error( "safe_loadTable4File: empty file:" .. filename );
		return {};
	end
	local ret = file:read("*all");
	file:close();
	return  FOO.safe_loadstring( ret, no_check_safe );
end;



---查看一个文件是否已经变化，返回true, false 和新文件的内容
FOO.file_content_cache = {}
FOO.check_file_content_is_changed = function( file_path_ )
	local ret_ = FOO.read_file_content_safe( file_path_ );
	if  FOO.file_content_cache[file_path_] and FOO.file_content_cache[file_path_] == ret_ then
		--未变化
		return false
	else
		--文件内容已经变化
		FOO.file_content_cache[file_path_] = ret_
		return true, ret_
	end
end



--table是否不为空
FOO.tableNotEmpty = function(t)
	if  type(t) == 'table' then
		return (next(t) ~= nil)
	else
		return false; --不是table
	end
end;


--table是空的
FOO.tableIsEmpty = function(t)
	if  type(t) == 'table' then
		return (next(t) == nil)
	else
		return false;  --不是table
	end
end

--判断类型是一个table
FOO.isTable = function(t)
	return (type(t) == 'table')
end


--按照key排序，获得连接value的字符串
FOO.getStringBySortKey = function( t )
	--log.debug( "call getStringBySortKey" );
	local tkey = {};
	for k, v in pairs( t ) do
		table_insert(tkey, k);
	end
	table_sort( tkey );
	local ret = "";
	for i=1, #tkey do
		ret = ret .. t[ tkey[i] ];
	end
	return ret;
end


--按照key排序，获得连接value的字符串，中间带有&=字符
FOO.getStringBySortKey2 = function(t)
	--log.debug( "call getStringBySortKey2" );
	local tkey = {};
	for k, v in pairs( t ) do
		table_insert(tkey, k);
	end
	table_sort( tkey );
	local ret = "";
	for i=1, #tkey do
		ret = ret .. tkey[i] .. "=" .. t[ tkey[i] ];
		if  i < #tkey then
			ret = ret .. "&";
		end
		--log.debug( "ret=" .. ret );
	end
	return ret;
end


--浅拷贝 不拷贝meta
FOO.table_value_copy = function (ori_tab)
    if  type(ori_tab) ~= "table" then
        return nil
    end
    local new_tab = {}
    for i, v in pairs(ori_tab) do
        if  type(v) == "table" then
            new_tab[i] = FOO.table_value_copy(v)
        else
            new_tab[i] = v
        end
    end
    return new_tab
end



--- 洗牌算法，用于将一组数据等概率随机打乱。等概率算法。
--- 参数 t 待打乱的数组table
function FOO.shuffle(t)
	math.randomseed( ngx.now() )
	if (not t) or type(t)~="table" then return end
	local cnt = #t
	for i=1,cnt do
		local j = math.random(1,cnt)
		t[i],t[j] = t[j],t[i]
	end
end


-- 检测一个table里的字段是否都存在
-- 所有字段齐全返回true
-- 否则返回false 和 第一个未知字段
FOO.table_check_fields = function( all_, fields_ )
	for k, v in pairs( fields_ ) do
		if  all_[v] == nil then
			log.debug( log.fast() and "check_fields fail:" .. v );
			return false, v;
		end
	end
	return true, nil;
end



FOO.get_time_stamp = function( str_ )
	--{year,month,day,hour,min,sec}
	local s_ret = 0;

	if str_ then
		if type(str_) == 'number' then
			return str_;
		elseif type(str_) == 'string' and #str_ >= 19 then
			--"2016/11/01 00:00:00",

			local ret = {};
			ret.year  = tonumber( string.sub( str_, 1, 4 ) )  or 2016;
			ret.month = tonumber( string.sub( str_, 6, 7 ) )  or 1;
			ret.day   = tonumber( string.sub( str_, 9, 10 ) ) or 1;

			ret.hour = tonumber( string.sub( str_, 12, 13 ) ) or 0;
			ret.min  = tonumber( string.sub( str_, 15, 16 ) ) or 0;
			ret.sec  = tonumber( string.sub( str_, 18, 19 ) ) or 0;

			s_ret = os.time( ret );  --带参数
		end

	end

	return s_ret;
end



-- worker加载期设置为保护模式，禁止掉一些日志函数
function FOO.set_init_protect_mode()
	ngx.ctx.timer_at = ngx.ctx.timer_at or 1
	ngx.ctx.log      = ngx.ctx.log      or 0
	ngx.ctx.m_params = ngx.ctx.m_params or {}
	ngx.ctx.xff      = ngx.ctx.m_params.xff or "init"
end



---------------------ngx.thread----------------
--- 请参考一下例子 uu.thread_spawn + uu.thread_wait (组合使用)
--- 在协程里并发执行
--- func_          执行主体函数
--- thread_list_   用来存放所有协程列表  [可选 nil表示不wait]
---
function FOO.thread_spawn( func_, thread_list_ )
	local thread_ = ngx_thread_spawn( func_ )
	if  thread_list_ then
		thread_list_[ #thread_list_ + 1 ] = thread_
		if  log.fast() then
			local cost_time_ = math.floor( (ngx.now() - ngx.req.start_time()) * 1000 + 0.5 )
			log.debug_list( "thread_spawn", #thread_list_, FOO.to_str(thread_), cost_time_ )
		end
	end
end


--- 等待所有协程执行完毕
--- thread_list_    协程列表
function FOO.thread_wait( thread_list_ )
	if  thread_list_ then
		for i=1, #thread_list_ do
			ngx_thread_wait( thread_list_[i] )
			if  log.fast() then
				local cost_time_ = math.floor( (ngx.now() - ngx.req.start_time()) * 1000 + 0.5 )
				log.debug_list( "thread_wait", i, FOO.to_str( thread_list_[i] ), cost_time_ )
			end
		end
	end
end



-- 拷贝指定字段number，如果存在-------------------------
function  FOO.set_num_field( t1, t2, fnames )
	if  t1 and t2 and fnames and type(t1)=='table' and type(t2)=='table' and type(fnames)=='table' then
		for k, v in pairs( fnames ) do
			if  t2[v] then
				--log.debug("set field:" .. v .. "=" .. t2[ v ] );
				t1[v] = tonumber( t2[v] );
			end
		end
	else
		log.error("call set_num_field, param=nil or not table");
	end
end


-- 拷贝指定字段string，如果存在
function  FOO.set_txt_field( t1,  t2,  fnames )
	if  t1 and t2 and fnames and type(t1)=='table' and type(t2)=='table' and type(fnames)=='table' then
		for k, v in pairs( fnames ) do
			if  t2[v] then
				--log.debug("set field:" .. v .. "=" .. t2[ v ] );
				t1[v] = t2[v];
			end
		end
	else
		log.error("call set_txt_field, param=nil or not table");
	end
end


-- 拷贝指定字段number，如果存在 返回有多少字段变化
function  FOO.set_num_field2( t1, t2, fnames )
	local change_ = 0
	if  t1 and t2 and fnames and type(t1)=='table' and type(t2)=='table' and type(fnames)=='table' then
		for k, v in pairs( fnames ) do
			if  t2[v] then
				--log.debug("set field:" .. v .. "=" .. t2[ v ] );
				local n_ = tonumber( t2[v] )
				if  t1[v] == n_ then
					--未变化
				else
					change_ = change_ + 1
					t1[v] = n_
				end
			end
		end
	else
		log.error("call set_num_field, param=nil or not table");
	end
	return change_
end



-- 拷贝指定字段string，如果存在 返回有多少字段变化
function FOO.set_txt_field2(  t1,  t2,  fnames )
	local change_ = 0
	if  t1 and t2 and fnames and type(t1)=='table' and type(t2)=='table' and type(fnames)=='table' then
		for k, v in pairs( fnames ) do
			if  t2[v] then
				--log.debug("set field:" .. v .. "=" .. t2[ v ] );
				if  t1[v] == t2[v] then
					--未变化
				else
					t1[v] = t2[v]
					change_ = change_ + 1
				end
			end
		end
	else
		log.error("call set_txt_field, param=nil or not table");
	end
	return change_
end



FOO.table2json = function ( t_ )
	return cjson_safe.encode(t_) or "";
end

FOO.json2table = function( str_ )
	return cjson_safe.decode( str_ ) or {};
end


FOO.stringIsJson = function( str_ )
	if  cjson_safe.decode( str_ ) then
		return true
	else
		return false
	end
end


FOO.stringIsLuaTable = function( str_ )
	if  table.loadstring( str_ ) then
		return true
	else
		return false
	end
end


-- 按缩进格式打印整个表结构
-- t: lua table
-- level: 层级
-- line_start:  一行的前缀，一般[四个空格]或者 [特殊字符输入法v1第一个]
-- line_end:    换行符  '\n' 或者 '<br>'
FOO.get_var_dump_string = function(t, level, line_start_, line_end_ )
	if  type(t) ~= 'table' then
		log.debug("---- table error ----type=" .. type(t) );
		return ''
	end

	if  level and level >= 16 then
		return ''  -- 无限循环保护
	end

	local fret_ = {}  --返回值

	local lv = level or 0
	if  lv == 0 then
		fret_[ #fret_ + 1 ] = '{'
	end

	-- 计算本行的前置空格对齐
	local pre_ = ''
	for ll=1, lv do
		pre_ = pre_ .. line_start_
	end

	local same_line_ = "";
	for i, v in pairs(t) do
		local k=i;
		if type(i) == 'number' then
			k = "[" .. i .. "]";
		end

		if  type(v) == 'table' then
			if  next(v) == nil then
				fret_[ #fret_ + 1 ] = pre_ .. line_start_ .. k .. " = {},"
			else
				fret_[ #fret_ + 1 ] = pre_ .. line_start_ .. k .. " = {"
				local tmp_ret_ = FOO.get_var_dump_string( v, lv+1, line_start_, line_end_ )
				fret_[ #fret_ + 1 ] = tmp_ret_
			end
		else
			if  type(v)=='string' then
				same_line_ = same_line_ .. k .. "=\"" .. v .. '\", '
			else
				same_line_ = same_line_ .. k .. "=" .. tostring(v) .. ', '
			end

			if  #same_line_ > 120 then
				fret_[ #fret_ + 1 ] = pre_ .. line_start_ .. same_line_
				same_line_ = ''
			end
		end
	end

	if  #same_line_ > 0 then
		fret_[ #fret_ + 1 ] = pre_ .. line_start_ .. same_line_
		same_line_ = ''
	end

	if  lv == 0 then
		fret_[ #fret_ + 1 ] = pre_ .. '}'
	else
		fret_[ #fret_ + 1 ] = pre_ .. '},'
	end

	return table_concat( fret_, line_end_ )
end


-- 适合浏览器显示的html格式
FOO.get_var_dump_string_html = function(t)
	return FOO.get_var_dump_string( t, nil, '&nbsp;&nbsp;&nbsp;&nbsp;', '<br>' )
end


-- 纯text格式封装
FOO.get_var_dump_string_text = function(t)
	return FOO.get_var_dump_string( t, nil, '    ', '\n' )
end


-- 新版本var_dump textarea
FOO.var_dump = function(t)
	local formed_str_ = FOO.get_var_dump_string( t, nil, '&nbsp;&nbsp;&nbsp;&nbsp;', '<br>' )
	log.debug( '====PRINT TABLE====<br>' .. formed_str_ )
end



-- 旧版本 var_dump_old
FOO.var_dump_old = function(t, level )
	if  not log.fast() then
		return
	end

	if  type(t) ~= 'table' then
		log.debug("---- table error ----type=" .. type(t) );
		return;
	end

	if  level and level >= 16 then
		return; -- 无限循环保护
	end

	local lv = level or 0
	if  lv == 0 then
		log.debug("-------- table --------");
		log.debug("{");
	end

    local __BLACK_SPACE__ = '　'   ---特殊字符输入法v1第一个

	local pre_ = ''
	for ll=1, lv do
		pre_ = pre_ .. __BLACK_SPACE__;
	end

	--log.debug( pre_ .. "{" );

	local same_line_ = "";
	for i, v in pairs(t) do
		local k=i;
		if type(i) == 'number' then
			k = "[" .. i .. "]";
		end

		if  type(v) == 'table' then
			log.debug( pre_ .. __BLACK_SPACE__ .. k .. " = {");
			FOO.var_dump_old( v, lv+1 );
		else
			same_line_ = same_line_ .. k .. "=" .. tostring(v) .. ', ';
		end
	end

	if  #same_line_ > 0 then
		log.debug( pre_ .. __BLACK_SPACE__ .. same_line_ );
		same_line_ = "";
	end

	log.debug( pre_ .. "}");
end



--检查table中是否含有function
function FOO.var_dump_has_func(t, level)

	if  not t then
		return false
	end

	if  level and level >= 8 then
		return true; -- 无限循环保护
	end

	local lv = level or 0

	for k, v in pairs( t ) do
		if      type(v) == 'function' then
			return true
		elseif  type(v) == 'table'    then
			if  FOO.var_dump_has_func( v, lv+1 ) then
				return true
			end
		else
			--nothing
		end
	end
	return false
end


------------------------------------------------------
---按格式返回 lua table or json
FOO.return_table = function( t )
	local ret_len_ = 0
	t = t or {};
	if  type(t) ~= 'table' then
		t = { ret=t };
	end
	if  ngx.ctx.m_params.json == '1' then
		--json ajax 跨域
		ngx.header["Access-Control-Allow-Origin"] = "*";
		ngx.header["Server"] = "php";
		--log.debug("return json");
		local jret = FOO.table2json( t );
		--- ngx.header["Content-Length"] = #jret   ---nginx会自动填，手动填会报错
		ngx.say(jret);
		ret_len_ = #jret
	elseif ngx.ctx.m_params.log == '1' then
		--log
		log.debug( "<br><br>=========================return begin========" );
		FOO.var_dump(t)
		log.debug( "=========================return end========<br><br>" );
	else
		--lua
		local ret = table.tostring( t );
		--- ngx.header["Content-Length"] = #ret   ---nginx会自动填，手动填会报错
		ngx.say( ret);
		ret_len_ = #ret
	end
	return ret_len_
end


--- 按格式返回(只获得string) lua table string
FOO.get_return_table = function( t )
	t = t or {};
	if  type(t) ~= 'table' then
		t = { ret=t };
	end
	if  not log.fast() then
		log.debug( "===========================return_table begin====================" );
		FOO.var_dump(t);
		log.debug( "===========================return_table end  ====================" );
	end

	local ret_
	if  ngx.ctx.m_params.json == '1' then
		--log.debug("return json");
		--json ajax 跨域
		ngx.header["Access-Control-Allow-Origin"] = "*";
		ngx.header["Server"] = "php";
		ret_ = FOO.table2json( t );
	else
		--log.debug("return lua table");
		ret_ = table.tostring( t );
	end

	--- ngx.header["Content-Length"] = #ret_   ---nginx会自动填，手动填会报错
	return ret_
end



--- 返回一个字符串string，可选设置返回码
FOO.return_string = function( str_, ret_code_ )
	--- ngx.header["Content-Length"] = #str_    ---nginx会自动填，手动填会报错
	if  ret_code_ then
		ngx.status = ret_code_
	end
	ngx.print( str_ );
end



-- 版本转换---------------------------------------
-- 2561  0.10.1
function FOO.ver2i( version )
	if  type(version) == 'number' then
		return version
	elseif  type(version) == 'string' then
		local t = FOO.split( version, "." );
		--log.debug( "v=" .. version .. ", t size=" .. #t );
		if #t == 1 then
			return tonumber( t[1] );                                                   --2561
		elseif #t == 3 then
			return tonumber(t[1])*256*256 + tonumber(t[2])*256 + tonumber(t[3]);       --0.10.1
		else
			--格式错误
		end
	end
	return 0;
end


-- 版本转换
-- 0.10.1   2561
function  FOO.i2ver( version )
	local a3 =  version % 256;
	local a2 = (version - a3) % 65536;
	local a1 = (version - a3 - a2 ) / 65536;
	a2 = a2 / 256;
	return a1 .. '.' ..  a2 .. '.' .. a3;
end


function FOO.split(s, delim)
    if type(delim) ~= "string" or string.len(delim) <= 0 then
        return
    end

    local start = 1
    local t = {}
    while true do
        local pos = string.find (s, delim, start, true) -- plain find
        if not pos then
          break
        end

        table_insert (t, string.sub (s, start, pos - 1))
        start = pos + string.len (delim)
    end
    table_insert (t, string.sub (s, start))

    return t
end


function FOO.trim (s)
	return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end


--执行命令 ( 会卡住worker )
function FOO.os_execute( cmd_ )
	if  string.find( cmd_, ';' )  or string.find( cmd_, '|' )  or string.find( cmd_, '&' ) or
	    string.find( cmd_, "\n" ) or string.find( cmd_, "\t" ) or string.find( cmd_, '`' ) then
		log.error( "cmd unsafe | cmd=" .. cmd_ );
		return 256;
	else
		local ret = os.execute( cmd_ );
		if  type(ret)=='boolean' then
			ret = ret and 0 or 256;
		end
		return ret;
	end
end


--安全命令 ( 会卡住worker )
function FOO.os_execute_safe( cmd_ )
	local ret = os.execute( cmd_ );
	if  type(ret)=='boolean' then
		ret = ret and 0 or 256;
	end
	return ret;
end



--执行命令 返回执行结果  ( 会卡住worker )
--[注意事项： 部分命令[php luac]会返回空，如果测试发现这种情况，则使用 os_exec_file 来替代 ]
function FOO.os_exec( cmd_ )
	log.debug( log.fast() and "os_exec=" .. cmd_ )
	local  p_ = io.popen( cmd_ )
	local  ret_ = p_:read("*all")
	log.debug( log.fast() and "os_exec ret=" .. FOO.to_str(ret_) )
	return ret_
end


--执行命令 返回执行结果 [ 旧版本，性能稍弱，可以使用 os_exec 来代替 ]  ( 会卡住worker )
--[注意事项：由于命令本身会进行 '>' 追加，所以命令中不能出现 '>' 符号 ]
function FOO.os_exec_file( cmd_ )
	log.debug( log.fast() and "os_exec=" .. cmd_ )
	FOO.exec_seq = FOO.exec_seq + 1
	if  FOO.exec_seq > 999 then FOO.exec_seq = 0 end

	local seq_ = FOO.exec_seq

	FOO.create_dir_if( "/dev/shm/__tmpLuaOsExec/" );  	--建立目录
	local file_ = "/dev/shm/__tmpLuaOsExec/__ret_" .. FOO.work_id .."_" .. seq_
	os.execute( "> " .. file_ )
	local ret_ = os.execute( cmd_ .. " > " .. file_ .. " 2>&1 " )
	if  type(ret_)=='boolean' then
		ret_ = ret_ and 0 or 256;
	end
	if  ret_ ~= 0 then
		log.debug( log.fast() and "ERROR ======= exe ret not 0, =" .. FOO.to_str(ret_) )
	else
		log.debug( log.fast() and "exe ret ok=" .. FOO.to_str(ret_) )
	end

	local ret_string_ = FOO.read_file_content( file_ );
	return ret_string_
end



-- 异步执行一个命令（不会卡住worker） 适合一些时间比较长的系统调用
-- 命令中不要出现分号[;] 可以用 && 来表示顺序执行
-- 例子： sleep 10  &&  ls > /tmp/111.log 2>&1 && wget http_xxxx
function FOO.os_exec_async( cmd_, timeout_ )
	log.debug( log.fast() and "os_exec=" .. cmd_ )

	FOO.exec_async_seq = FOO.exec_async_seq + 1
	if  FOO.exec_async_seq > 999 then FOO.exec_async_seq = 0 end

	local seq_ = FOO.exec_seq

	FOO.create_dir_if( "/dev/shm/__tmpLuaOsExec/" );  	--建立目录
	local file_ = "/dev/shm/__tmpLuaOsExec/__ret_async_" .. FOO.work_id .."_" .. seq_
	os.execute( "> " .. file_ .. '.over && > ' .. file_ )
	local ret_ = os.execute( cmd_ .. " > " .. file_ .. " 2>&1 && echo " .. seq_ .. " > " .. file_ .. '.over &' )
	if  type(ret_)=='boolean' then
		ret_ = ret_ and 0 or 256;
	end
	if  ret_ ~= 0 then
		log.debug( log.fast() and "ERROR ======= exe ret not 0, =" .. FOO.to_str(ret_) )
	else
		log.debug( log.fast() and "exe ret ok=" .. FOO.to_str(ret_) )
	end

	---等待 file.over 生成
	local timeout_ = timeout_ or 30
	local wait_    = 0.01   ---每次等待时间
	while timeout_ > 0 do
		if  FOO.get_file_size( file_ .. '.over' ) > 0 then
			log.debug( log.fast() and "exe ret ok, time left=" .. timeout_ )
			break
		--else
			--log.debug( log.fast() and "exe ret wait, time left=" .. timeout_ )
		end

		timeout_ = timeout_ - wait_
		if  timeout_ < 0 then
			log.debug( log.fast() and "exe ret __TIMEOUT__" )
			return "__TIMEOUT__"   ---超时
		end

		if  wait_ == 1 then
			---不变化
		elseif  wait_ < 1 then
			wait_ = wait_ * 2
		else
			wait_ = 1
		end

		ngx.sleep( wait_ )
	end

	local ret_string_ = FOO.read_file_content( file_ );
	return ret_string_
end





--是否在命令期间
FOO.in_check_time = function( inter )
	local call_tools_time_ = FOO.get_nginx_share( "call_tools_time" ) or 0;
	local now  = FOO.now();
	log.debug( log.fast() and call_tools_time_ .. ' / ' .. now );
	if  now <= call_tools_time_  then
		ngx.say( "-------------------------------------------------------------------------------<br>" );
		ngx.say( "此命令受调用频率限制，还有" .. call_tools_time_ - now  .. "秒后才能执行。<br>" );
		ngx.say( "-------------------------------------------------------------------------------<br>" );
		return true;
	else
		FOO.set_nginx_share( "call_tools_time", now+inter);
		return false;
	end
end


--全局变量
FOO.set_nginx_share = function( k, v )
	if  k and v then
		ngx_shared.miniw:set( k, v );
		log.debug( log.fast() and "set miniw share: " .. k .. " / " .. v );
	end
end


FOO.get_nginx_share = function( k )
	local ret;
	if  k then
		ret = ngx_shared.miniw:get( k );
		--log.debug( "get miniw share: " .. k .. " / " .. FOO.to_str(ret) );
		--FOO.report_share_free_space( ngx_shared.miniw, "miniw", k )
	end
	return ret;
end



-- 上报共享内存的使用率(会判断时间间隔)
-- !!! 性能比较差， 已经废弃 !!!
-- 请使用新的定时器判断， 参考 ns_share_dict_checker.start_check_by_timer 的例子
FOO.free_space_report_time = {}
FOO.report_share_free_space = function( share_, dict_name_, key_, log_name_ )
	if  FOO.work_id > 3 then
		return
	end

	if  share_.free_space then
		local now_ = FOO.now()

		if  not FOO.free_space_report_time[ dict_name_ ] then
			FOO.free_space_report_time[ dict_name_ ] = now_ + FOO.work_id
		elseif  now_ > FOO.free_space_report_time[ dict_name_ ] then
			local f1_ = share_:free_space()
			local c1_ = share_:capacity()
			local rate_ = f1_/c1_
			if  rate_ > 0.99 then
				-- 使用率不高减少打印和检测次数
				-- FOO.free_space_report_time[ dict_name_ ] = now_ + 600 + FOO.work_id
			elseif rate_ < 0.2 then
				-- 空间告警
				FOO.free_space_report_time[ dict_name_ ] = now_ + 120 + FOO.work_id
				local r_ = string.format("%.4f", rate_ );
				log.day_list_simple( "dict_free", "error", FOO.work_id,
						log_name_, dict_name_, c1_/(1024*1024), r_, key_ )
				log.error( "shared_free_space rate=" .. r_ .. " for " .. dict_name_ )
			else
				-- 正常使用
				FOO.free_space_report_time[ dict_name_ ] = now_ + 300 + FOO.work_id
				local r_ = string.format("%.4f", rate_ );
				log.day_list_simple( "dict_free", FOO.work_id,
						log_name_, dict_name_, c1_/(1024*1024), r_, key_ )
			end
		end
	end
end



--获得秒级别now  类似os.time()
FOO.now = function()
	return ngx.time()
end


--当前实时
FOO.now_fresh = function()
	ngx.update_time()
	return ngx.time()
end


--当前实时秒,带小数
FOO.now_fresh_float = function()
	ngx.update_time()
	return ngx.now()
end


-- 获得每N秒的整点时间，给mysql使用，便于被mysql语句缓存
-- FOO.now_mysql( 60 )    ---每60秒整点(每分钟)
-- FOO.now_mysql( 600 )   ---每10分钟
-- FOO.now_mysql( 3600 )  ---每小时整点
FOO.now_mysql = function( inter_ )
	local now_ = ngx.time()
	return now_ - ( now_ % (inter_ or 600) )
end



--比较两个时间是否是同一天
FOO.compareTimes = function(time1,time2)
	local t1 = os.date("*t",time1)
	local t2 = os.date("*t",time2)
	if t1.year == t2.year then
		if t1.month == t2.month then
			if t1.day == t2.day then
				return true
			else
				return false
			end
		else
			return false
		end
	else
		return false
	end
end



---检查单个worker内的锁N秒  ( 不能跨worker进程 )
---pname_:  锁的名字
---pexp_:   锁时长(秒)
---返回值:   true=检查成功并获得锁     false=仍然处于被锁状态
FOO.check_worker_time_interval = function( pname_, pexp_ )
	if  pname_ and pexp_ then
		local now_   = FOO.now()
		local check_ = FOO.time_locks[ pname_ ] or 0
		if  now_ - check_ > pexp_ then
			FOO.time_locks[ pname_ ] = now_
			return true
		end
	else
		log.error("check_worker_time_interval param error")
	end
	return false
end


---检查server锁N秒 (非常低的几率重入 跨worker进程加锁)
---pname_ 锁的名字
---pexp_:   锁时长(秒)
---返回值:   true=检查成功并获得锁     false=仍然处于被锁状态
FOO.check_server_time_interval = function( pname_, pexp_ )
	--1 先worker检测一次
	if  FOO.check_worker_time_interval( pname_, pexp_ ) then
		local now_   = FOO.now()
		--2 共享 shared dict检测
		local check_ = ngx_shared.miniw:get( pname_ ) or 0;
		if  now_ - check_ > pexp_ then
			ngx_shared.miniw:set( pname_, now_, pexp_*2 )
			return true
		end
	end

	return false
end



--设置全局变量
FOO.set_nginx_share2 = function( name, k, v )
	if  name and k and v then
		local share_ = ngx_shared[name];
		if  share_ then
			share_:set( k, v );
			log.debug( log.fast() and "set share: " .. name .. " / " .. k .. " / " .. v );
		else
			log.error( "not found share: " .. name );
			ngx.exit( ngx.ERROR );
		end
	end
end


--ngx_shared.DICT.flush_all
FOO.flush_all_nginx_share2 = function( name )
	if  name then
		local share_ = ngx_shared[name];
		if  share_ then
			share_:flush_all();
			log.debug( log.fast() and "flush_all share: " .. name );
		else
			log.error( "not found share: " .. name );
			ngx.exit( ngx.ERROR );
		end
	end
end



FOO.get_nginx_share2 = function( name, k )
	local ret;
	if  name and k then
		local share_ = ngx_shared[name];
		if  share_ then
			ret = share_:get( k );
			log.debug( log.fast() and "get share: " .. k .. " / " ..FOO.to_str(ret) );
			--FOO.report_share_free_space( share_, name, k )
		else
			log.error( "not found share: " .. name );
			ngx.exit( ngx.ERROR );
		end
	end
	return ret;
end


--清理全局变量
FOO.del_nginx_share2 = function( name, k )
	if  name and k then
		local share_ = ngx_shared[name];
		if  share_ then
			share_:delete( k );
			log.debug( log.fast() and "delete share: " .. name .. " / " .. k );
		else
			log.error( "del not found share: " .. name );
		end
	end
end


-----批量获得share dict中的key列表
FOO.get_nginx_share_table = function( name, max_ )
	local ret;
	if name and max_ then
		local share_ = ngx_shared[name];
		if  share_ then
			--FOO.report_share_free_space( share_, name, "get_keys" )
			ret = share_:get_keys( max_ );
		else
			log.error( "not found miniw share: " .. name );
			ngx.exit( ngx.ERROR );
		end
	end
	return ret;
end



--设置全局数据缓存kv table 带失效期 十分表
FOO.set_server_cache_table = function( select_, key_, t_, exp_time_ )
	if  select_ and key_ and t_ and exp_time_ then
		local v_ = table.tostring( t_ )
		local dict_name_ = "miniw" .. (select_%10);
		local share_ = ngx_shared[ dict_name_ ];
		if  share_ then
			local ok, err, forcible = share_:set( key_, v_, exp_time_ );
			if  ok then
				if  forcible then
				    log.day_list( "cache_hit", "set", dict_name_, key_, err, "forcible" )
				end
			else
				log.day_list( "error", "set", dict_name_, key_, err )
				log.error( "set_server_cache_table: " .. dict_name_ .. " " .. key_ .. " " .. FOO.to_str(err) );
				ngx.exit( ngx.ERROR );
			end
		else
			log.error( "set_server_cache_table: no dict " .. dict_name_);
			ngx.exit( ngx.ERROR );
		end
	end
end


--获得全局数据缓存中的table 十分表
FOO.get_server_cache_table = function( select_, key_ )
	if  select_ and key_ then
		local dict_name_ = "miniw" .. (select_%10);
		local share_ = ngx_shared[ dict_name_ ];
		if  share_ then
			--FOO.report_share_free_space( share_, dict_name_, key_ )
			local v_ = share_:get( key_);
			if  v_ then
				local ret_table_ = table.loadstring( v_ );
				if  type(ret_table_) == 'table' then
					--log.day_list( "cache_hit", "get", dict_name_, key_ )
					return ret_table_
				else
					log.error( "get_server_cache_table: ret not table=" .. dict_name_ .. " " .. key_ .. " " .. v_ );
					ngx.exit( ngx.ERROR );
				end
			end
		else
			log.error( "get_server_cache_table: no dict " .. dict_name_);
			ngx.exit( ngx.ERROR );
		end
	end
	return nil
end




--设置全局数据缓存kv string 带失效期 十分表
FOO.set_server_cache_string = function( select_, key_, v_, exp_time_ )
	if  select_ and key_ and v_ and exp_time_ then
		local dict_name_ = "miniw" .. (select_%10);
		local share_ = ngx_shared[ dict_name_ ];
		if  share_ then
			local ok, err, forcible = share_:set( key_, v_, exp_time_ );
			if  ok then
				if  forcible then
				    log.day_list( "cache_hit", "set", dict_name_, key_, err, "forcible" )
				end
			else
				log.day_list( "error", "set", dict_name_, key_, err )
				log.error( "set_server_cache_string: " .. dict_name_ .. " " .. key_ .. " " .. FOO.to_str(err) );
				ngx.exit( ngx.ERROR );
			end
		else
			log.error( "set_server_cache_string: no dict " .. dict_name_);
			ngx.exit( ngx.ERROR );
		end
	end
end


--获得全局数据缓存中的table 十分表
FOO.get_server_cache_string = function( select_, key_ )
	if  select_ and key_ then
		local dict_name_ = "miniw" .. (select_%10);
		local share_ = ngx_shared[ dict_name_ ];
		if  share_ then
			--FOO.report_share_free_space( share_, dict_name_, key_ )
			local v_ = share_:get( key_);
			if  v_ then
				return v_
			end
		else
			log.error( "get_server_cache_string: no dict " .. dict_name_);
			ngx.exit( ngx.ERROR );
		end
	end
	return nil
end


--set ttl (expire函数) (miniw0 to miniw9) ( seconds )
FOO.set_ttl_server_cache = function( select_, key_, et_ )
	if  select_ and key_  then
		local dict_name_ = "miniw" .. (select_%10);
		local share_ = ngx_shared[ dict_name_ ];
		if  share_ then
			share_:expire( key_, et_ )    --需要 require "resty.core"
		else
			log.error( "set_ttl_server_cache: no dict " .. dict_name_);
			ngx.exit( ngx.ERROR );
		end
	end
end


--get ttl (miniw0 to miniw9) ( seconds )
FOO.get_ttl_server_cache = function( select_, key_ )
	if  select_ and key_  then
		local dict_name_ = "miniw" .. (select_%10);
		local share_ = ngx_shared[ dict_name_ ];
		if  share_ then
			local ttl_, err = share_:ttl( key_ )  --需要 require "resty.core"
			if  ttl_ then
				return ttl_
			else
				if  err then
					log.error( 'get_ttl_server_cache:' .. err )
				end
			end
		else
			log.error( "get_ttl_server_cache: no dict " .. dict_name_);
			ngx.exit( ngx.ERROR );
		end
	end
end


--清理
FOO.clean_server_cache_table = function( select_, key_ )
	if  select_ and key_  then
		local dict_name_ = "miniw" .. (select_%10);
		local share_ = ngx_shared[ dict_name_ ];
		if  share_ then
			share_:delete( key_ );
		else
			log.error( "clean_server_cache_table: no dict " .. dict_name_);
			ngx.exit( ngx.ERROR );
		end
	end
end



--- 任务状态管理( 跨worker任务， worker直接的任务互斥 )
FOO.task_status = {}


--- 当前任务是否还在继续
--- 返回字符串表示正忙
--- 返回空表示任务已经完成
function FOO.task_status.isDoing( select_, key_ )
	return FOO.get_server_cache_string( select_, key_ )
end


---设置当前任务正在进行, 默认超时120秒
function FOO.task_status.setDoing( select_, key_, v_, exp_time_ )
	FOO.set_server_cache_string( select_, key_, v_, (exp_time_ or 120) )
end



--------------------------------file------------------------------

FOO.read_file_content = function( file_path_name, mode_ )
	local file = io.open( file_path_name, mode_ or "r" );
    assert(file);
    local ret = file:read("*all");
	file:close();
	return ret;
end


FOO.read_file_content_safe = function( file_path_name, mode_ )
	local file = io.open( file_path_name, mode_ or "r" );
	if  not file then
		return "";
	else
		local ret = file:read("*all");
		file:close();
		return ret;
	end
end


---读取二进制
FOO.read_bin_file_content = function( file_path_name )
	local file = io.open( file_path_name, "rb" );
	assert(file);

	local len_ = file:seek("end");
	file:seek("set", 0);

	local ret = file:read(len_);
	file:close();
	return ret, len_;
end


---读取二进制文件，出错不退出
FOO.read_bin_file_content_safe = function( file_path_name )
	local file = io.open( file_path_name, "rb" );
	if  not file then
		return "", 0;
	end

	local len_ = file:seek("end");
	file:seek("set", 0);

	local ret = file:read(len_);
	file:close();
	return ret, len_;
end



--存成cache文件
FOO.write_file_content = function( file_path_name, content, flush_ )
	if  file_path_name and content then
		local file = io.open(file_path_name, "w");
		if  file then
			file:write( content );
			if  flush_ then
				file:flush();
			end
			file:close();
		end
	end
end


--存table到文件
FOO.save_table_file = function( fn_, t_ )
	if  t_ and type(t_)=='table' then
		local c_ = table.tostring( t_ )
		log.debug( log.fast() and "save_table_file:" .. fn_ .. " ==> " .. c_ )
		FOO.write_file_content( fn_, c_, true )  --flush=true
	end
end


--从文件中读取table
FOO.read_table_file = function( fn_ )
	if  FOO.file_exist(fn_) then
		local  c_ = FOO.read_file_content_safe( fn_ )
		log.debug( log.fast() and "read_table_file:" .. fn_ .. " ==> " .. c_ )
		local  ret_table_ = table.loadstring( c_ );
		return ret_table_
	end
	return nil
end

--移动文件
FOO.mv_file = function( f1, f2 )
	if  f1 and f2 then
		local ret_exe = FOO.os_execute( "mv " .. f1 .. " " .. f2 );
		log.debug( "mv " .. f1 .. " " .. f2 .. ", ret="  .. FOO.to_str(ret_exe) );
		return ret_exe;
	end
end


--解压文件
--targets_:   解压指定的文件或文件夹;  无targets_，则全部解压
FOO.unzip = function( zip_, dir_, targets_ )
	if  zip_ and dir_ then
		local cmd_ = "unzip -qo " .. zip_
		if  targets_ then
			cmd_ = cmd_ .. " " .. targets_
		end
		cmd_ = cmd_ .. " -d " .. dir_

		local ret_exe = FOO.os_execute( cmd_ );
		log.debug( cmd_ .. ", ret="  .. FOO.to_str(ret_exe) );
		return ret_exe;
	else
		log.error( "unzip error_params" )
	end
end

-------- add tostring and loadstring to table --------
table.tostring = function(t)
	local mark={}
	local assign={}

	local function ser_table(tbl,parent)
		mark[tbl]=parent
		local tmp={}
		for k, v in pairs(tbl) do
			local key= type(k)=="number" and "["..k.."]" or "[".. string_format("%q", k) .."]"
			if type(v)=="table" then
				local dotkey= parent .. key
				if mark[v] then
					table_insert(assign, dotkey .. "=" .. mark[v])
				else
					table_insert(tmp, key .. "=" .. ser_table(v,dotkey))
				end
			elseif type(v) == "string" then
				table_insert(tmp, key .. "=" .. string_format('%q', v))
			elseif type(v) == "number" or type(v) == "boolean" then
				table_insert(tmp, key .. "=" .. tostring(v))
			end
		end
		return "{" .. table_concat(tmp,",") .. "}"
	end

	return ser_table(t,"ret") .. table_concat(assign, "")
end


table.loadstring = function(strData)
	local f = loadstring("do local ret=" .. strData .. " return ret end")
	if f then
	   return f()
	end
end



---在一个顺序表t中，查找一个值v，并返回位置，没有则返回nil
FOO.index_list_table = function ( t_, v )
	if  t_ then
		for i=1, #t_ do
			if  t_[i] == v then
				return i
			end
		end
	end
end


---在一个顺序表t中，删除一个值v,并返回位置，没有则返回nil
FOO.remove_list_table = function ( t_, v )
	if  t_ then
		for i=1, #t_ do
			if  t_[i] == v then
				table.remove( t_, i )
				return i
			end
		end
	end
end



----------------------files---------
--touch
FOO.touch_file = function(full_path_)
	local ret_exe = FOO.os_execute( "touch " .. full_path_ );
	log.debug( log.fast() and "touch " .. full_path_ .. ", ret="  .. FOO.to_str(ret_exe) );
	return ret_exe;
end

--cp
FOO.cp_file = function(f1, f2)
	local ret_exe = FOO.os_execute( "cp " .. f1 .. " " .. f2 );
	log.debug( log.fast() and "cp " .. f1 .. " " .. f2 .. ", ret="  .. FOO.to_str(ret_exe) );
	return ret_exe;
end

-- 建立一个目录(如果不存在)
FOO.create_dir_if = function( dir_ )
	local tmp_path = io.open(dir_, "r");
	if  tmp_path then
		tmp_path.close();
	else
		local ret_exe = FOO.os_execute( "mkdir -p " .. dir_ );
		log.debug( log.fast() and "create dir=" .. dir_ .. ", ret="  .. FOO.to_str(ret_exe) );
	end
end


-- 改动一个文件或者目录的属性
FOO.chmod = function( file_, mod_ )
	local ret_exe = FOO.os_execute( "chmod " .. mod_ .. " " .. file_ );
	log.debug( log.fast() and "chmod " .. file_ .. ", ret="  .. FOO.to_str(ret_exe) );
end



FOO.get_file_size = function ( file_name_ )
	local fh = io.open( file_name_, "rb");
	if fh then
		local len_ = fh:seek("end");
		fh:close();
		return len_;
	else
		return -1;
	end
end


FOO.get_dir_size = function( dir_ )
	local ret_ = 0
	if dir_ then
		local ret_line_ = FOO.os_exec( "du -b -d0 " .. dir_ .. " | awk '{printf $1}' " )
		return tonumber( ret_line_ ) or 0
	end
	return ret_
end


FOO.get_file_md5 = function( fname )
	local  file_md5_
	if  FOO.get_file_size( fname ) > 10485760 then  --大于10M
		file_md5_ = FOO.os_exec( "md5sum " .. fname .. " | awk '{printf $1}' " )
		--log.debug( "get_file_md5 : "  .. file_md5_ )
	else
		file_md5_ = ngx.md5(FOO.read_file_content( fname ));
	end
	return file_md5_;
end


FOO.file_exist = function( path_ )
	local file = io.open(path_, "rb")
	if file then file:close() end
	return file ~= nil
end


--获得文件的建立时间
FOO.get_file_create_time = function( path_ )
	ngx.sleep(0.001)    --重复多次获得的时候，系统会卡，需要释放cpu
	local ret_ = FOO.os_exec( "stat -c %Y " .. path_ )
	return ret_
end



--在被 sp_ 分割的字符串中找出第N个数字
--uu.get_n_number_from_string( 10001_123, 1, '_' )
function FOO.get_n_number_from_string( str_, n_, sp_ )
	if  type( str_ ) == 'string' then
		local t = FOO.split( str_, sp_ );
		if  t and  t[n_] then
			return tonumber(t[n_])
		end
	end
	return nil
end


--阶梯设置变量默认值，默认节点全部设置为table
--eg  set_table_by_path( t, a, b, c )
--    t[a][b][c] = {}
function FOO.set_table_by_path( t, ... )
	local root_ = t
	if  type(root_) == 'table' then
		for i = 1, select("#", ...) do
			local arg = select(i, ...)
			if  root_[arg] == nil then
				root_[arg] = {}
			end
			root_ = root_[arg]
		end
		return root_
	else
		return nil
	end
	return root_
end



--阶梯查看变量table是否存在，所有节点都必须是table类型
--eg  check_table_has_path( t, a, b, c )
--    t[a][b][c]
function FOO.check_table_has_path( t, ... )
	local root_ = t
	if  type(root_) == 'table' then
		for i = 1, select("#", ...) do
			local arg = select(i, ...)
			if  root_[arg] and type( root_[arg] ) == 'table' then
				root_ = root_[arg]
			else
				return nil
			end
		end
	else
		return nil
	end
	return root_
end


--将url中传输的base64 string 转成标准的base64
--标准base64中，有几个字符是保留字: [ + / = ] 无法放在url中，
--客户端对此进行了转换，现在转回来
function FOO.base64_url2std( str64_ )
	local s_ = string.gsub(str64_,"-","+")
		  s_ = string.gsub(s_    ,";","/")
		  s_ = string.gsub(s_    ,"_","=")
	return s_
end


--map md5 to hash
--[[
unsigned int MapMd5Hash(string md5)
{
	uint32_t b = 1997;
	uint32_t a = 10613;
	uint32_t hash = 0;
	uint32_t long_hash = 0;
	string md5 = "61514364f4fa6659783760f18a1ce8bb";
	for (int i = 3; i<12; i++)  //123 [45678901] 234  [4到11位]
	{
		if ( i<md5.length() ) {
			long_hash = hash * a + (uchar)md5[i];
			hash  = long_hash % (uint32_t)47737;
			a = (a*b) % (uint32_t)49891;
		}
	}
	return long_hash;
}
--]]


function FOO.map_md5_to_hash( md5_ )
	local a, b, hash, long_hash = 10613, 1997, 0, 0
	for i=4, 12 do   --c++ [3 to 11]
		if  i < #md5_ then
			local char_ = string_byte( string_sub( md5_, i, i ) )
			long_hash = hash * a + char_;
			hash = long_hash % 47737
			a    = ( a*b ) % 49891
			--log.day_list( "map_md5_to_hash", i, long_hash, hash, a, b )
		end
	end
	return long_hash
end


--二进制数据转uint 小头在前 大头在前 两种结果都返回 len_数据size
function FOO.buff_to_uint( buff_, len_ )
	local n_={ 0, 0, 0, 0 }
	for i=1, 4 do
		local c_ = string_sub( buff_, i, i )
		if  c_ then
			n_[i] = tonumber( string_byte(c_) )
		end
	end

	local ret1_, ret2_, k = 0, 0, 1
	for i=1, 4 do
		ret1_ = ret1_ + n_[i]   * k  --小头在前
		ret2_ = ret2_ + n_[5-i] * k  --大头在前
		--log.day_list( "upload", "buff_to_uint", n_[1], n_[2], n_[3], n_[4], k,  ret1_, ret2_ )
		k = k * 256
	end

	return ret1_, ret2_	
end



--二进制数据转uint 小头在前 大头在前 两种结果都返回 len_数据size
--与buff_to_uint的区别： 带有偏移量参数
function FOO.buff_to_uint_offset( buff_, offset_ )
	local n_={ 0, 0, 0, 0 }
	for i=1, 4 do
		local c_ = string_sub( buff_, i+offset_, i+offset_ )
		if  c_ then
			n_[i] = tonumber( string_byte(c_) )
		end
	end

	local ret1_, ret2_, k = 0, 0, 1
	for i=1, 4 do
		ret1_ = ret1_ + n_[i]   * k  --小头在前
		ret2_ = ret2_ + n_[5-i] * k  --大头在前
		--log.day_list( "upload", "buff_to_uint", n_[1], n_[2], n_[3], n_[4], k,  ret1_, ret2_ )
		k = k * 256
	end

	return ret1_, ret2_
end




--获得gm等级  0=非管理员 1=特殊号码 2=管理员 3=超管
FOO.gm_acc = {
	[1000] = 3,
	[1000001000] = 3,

	[1001] = 2,
	[1000001001] = 2,

	[1002] = 2,
	[1000001002] = 2,
	[1000001120] = 2,

	[1007596107] = 1,
	[1007410582] = 1,
	[1007360927] = 1,
	[1008967696] = 1,
	[1048972472] = 1,
	[1035278741] = 1,
	[1008982600] = 1,
	[1043570996] = 1,

	[1000010030] = 1,
	[1007352054] = 1,
	[1026587247] = 1,
	[1013020737] = 1,
	[1024219201] = 1,
	[1007453859] = 1,
	[1024219201] = 1,
	[1032521679] = 1,
	[1008177470] = 1,
	[1008711149] = 1,
	[1033198020] = 1,
	[1011202405] = 1,
	[1013277909] = 1,
	[1033198020] = 1,
	[1013277909] = 1,
	[1011202405] = 1,

	---------------------------------------
	[12345] = 2,

	--  [13011401] = 1,
	--[1013011401] = 1,

	[1005]     = 3,
	[2259]     = 3,      --select rank
	[1103]     = 1,      --官方赛事 萌宝

	[447845141]  = 1,    --可以查看黑名单内容
	[100903256]  = 1,
	[333778370]  = 1,
	[259505398]  = 1,

	--[899993838] = 1,
	--[808342345] = 1,
	--[922435926] = 1,

	[1166202] = 1,
}



FOO.get_gm_level = function( uin_ )	
	if  uin_ >=1000 and uin_ < 5000 then
		return 1
	end

	if  uin_ and FOO.gm_acc[uin_] then
		return FOO.gm_acc[uin_]
	else
		return 0
	end	
end


--获得接口名
FOO.get_act_cmd = function()
	if  ngx.ctx.m_params then
		return (ngx.ctx.m_params.act or ngx.ctx.m_params.cmd or "nil")
	else
		return ""
	end
end


FOO.get_map_path = function ( node_, dir_, md5_, ext_ )
	local ret_ = node_ .. "/" .. dir_ .. "/" .. md5_
	if  ext_ then
		if  string_sub( ext_, 1, 1 ) == '.' then
			ret_ = ret_ .. ext_
		else
			ret_ = ret_ .. "." .. ext_
		end
	end
	return ret_
end


---nil, boolean, number, string, userdata, function, thread, table
---获得可以打印的数据，只用来打日志使用
FOO.to_str = function( v )
	local t_ = type(v)
	if  t_ == 'string' or t_ == 'number' then
		return v .. "(" .. t_ .. ")"
	elseif t_ == 'nil' then
		return 'nil' .. "(" .. t_ .. ")"
	elseif  t_ == 'boolean' then
		if  v then
			return 'true' .. "(" .. t_ .. ")"
		else
			return 'false' .. "(" .. t_ .. ")"
		end
	elseif t_ == 'table' then
		return table.tostring(v) .. "(" .. t_ .. ")"
	elseif v == ngx.null then
		return "not_found_ngx_null" .. "(" .. t_ .. ")"
	else
		return tostring(v) .. "(" .. t_ .. ")"
	end
end


---是否是黑名单或者注销状态
--- black      --0=不违规  1=违规（旧版本）
--- black_stat --0=不违规  1=审核 2=违规 (可携带black_time)
--- unregister_time  注销时间戳
FOO.is_in_black_stat = function( black_table_ )
	if  type(black_table_) == 'table' then

		if  black_table_.open_svr == 4 then
			--ns_const_var FAIL  = 4,    ---审核失败     (都不可见)
			return true
		end

		if  black_table_.black == 1 then
			return true   --旧版本永久黑名单
		end

		if  black_table_.unregister_time then
			return true   --已注销标记
		end

		if  	black_table_.black_stat == 2 then
			if  black_table_.black_time then
				if  black_table_.black_time == 0 then
					return true   --永久
				elseif  FOO.now() < black_table_.black_time then
					return true   --期限内
				end
			else
				return true   --没有black_time永久封
			end
		end
	end
	return false
end


---判断profile资料是否黑名单或者注销状态 ( 比前一个函数is_in_black_stat判断的条件更少 )
FOO.is_in_black_stat_profile = function( black_table_ )
	if  type(black_table_) == 'table' then
		if  black_table_.black_stat == 2 then
			return true
		end
	end
	return false
end


--是否是白名单期间内
FOO.is_in_white_stat = function( white_table_ )
	if  type(white_table_) == 'table' then
		if  	white_table_.white_stat == 1 then
			if  white_table_.white_time then
				if  white_table_.white_time == 0 then
					return true   --永久
				elseif  FOO.now() < white_table_.white_time then
					return true   --期限内
				end
			end
		end
	end
	return false
end



--是否是状态回退 black_stat
FOO.is_stat_reverse = function( old_stat, new_stat )
	if  ngx.ctx.m_params.gm_name then
		return false   --gm可以随便修改状态
	end
	if  old_stat > new_stat then   --2to1  1to0
		return true
	end
	return false
end



---report_cc report_t ( 不会清理其他数据 )
---判断被举报的玩家时间是否过期
FOO.check_report_cc_t = function( t_, time_ )
	local now_ = FOO.now()

	if  type(t_) == 'table' then
		if  not t_.report_cc then
			    t_.report_cc = 0
		end
		if  not t_.report_hzb_cc then
			    t_.report_hzb_cc = 0
		end
		if  not t_.report_t then
			    t_.report_t = now_
		end
		if  now_ - t_.report_t > time_ then
			t_.report_cc     = 0
			t_.report_hzb_cc = 0
			t_.report_t = now_
		end
	end

end


---根据过期时间重制次数
--- clean_flag_ 过期是否清理整个变量
FOO.check_table_cc_t = function( root_, name_, time_, clean_flag_ )
	local now_ = FOO.now()
	if  type(root_) == 'table' then

		if  not root_[name_] then
			root_[name_] = { t=now_, cc=0 }
		end

		if  clean_flag_ then
			--过期清理所有
			if  not ( root_[name_].cc and root_[name_].t ) then
				root_[name_] = { t=now_, cc=0 }
			elseif now_ - root_[name_].t > time_ then
				root_[name_] = { t=now_, cc=0 }
			end

		else
			--过期只重置cc和t
			if  not root_[name_].cc then
				root_[name_].cc = 0
			end
			if  not root_[name_].t then
				root_[name_].t = now_
			end
			if  now_ - root_[name_].t > time_ then
				root_[name_].cc = 0
				root_[name_].t = now_
			end

		end
	end
end



---检测一个pid文件里的pid的进程是否存在
FOO.check_pid_is_alive = function( pid_file_name__, exe_name_ )
	local str_pid_ = FOO.read_file_content_safe( pid_file_name__ )
	if  #str_pid_ <= 0 then
		return  false   ---pid文件不存在或者为空
	end

	log.debug( log.fast() and "str_pid_=[" .. str_pid_ .."]" )
	local pid_ = tonumber(str_pid_) or 0
	log.debug( log.fast() and "pid=[" .. pid_ .."]" )
	if  pid_ and pid_ > 0 then
		local cmd_ = "ps -p " .. pid_ .. " | grep " .. exe_name_
		local ret_ = FOO.os_exec( cmd_ )
		log.debug( log.fast() and "os_exec ret=[" .. ret_ .. "]" )
		if  ret_ and string.find( ret_, exe_name_ ) then
			return true, pid_
		end
	else
		log.debug( log.fast() and "pid file not exist")
		return false, pid_
	end
	return false, pid_
end



---是否是手机号码
FOO.isMobileNum = function( s_ )
	local n_ = tonumber(s_) or 0
	return (n_>13000000000 and n_<19999999999)
end


---设置json的跨域安全
FOO.set_json_CORS = function()
	if  ngx.ctx.m_params.json == '1' then
		--json ajax 跨域
		ngx.header["Access-Control-Allow-Origin"] = "*";
		ngx.header["Server"] = "php";
	end
end


--- 拆分id格式  uin+time时间戳组合 : 12345_1122223333
function FOO.check_uin_time( uin_time_ )
	local t_ = FOO.split( uin_time_, '_' )
	if  t_ and #t_ == 2 then
		local uin_  = tonumber( t_[1] ) or 0
		local time_ = tonumber( t_[2] ) or 0
		if  uin_ < 1000 or time_ < 1000000000 then
			log.error( "error_uin_time:" .. uin_time_ )
			return false
		end
		return true, uin_, time_
	end
	return false
end


---获得远端IP
function FOO.getRemoteIP()
	--return type(ngx.ctx.xff) .. '_' .. type(ngx.var.remote_addr)
	return (ngx.ctx.xff or ngx.var.remote_addr or "nil")
end



---重新加载一个lua文件
function FOO.reloadLuaFile( lua_file_name, need_reload_ )
	if  (ngx.ctx.m_params.reload == '1') or (need_reload_ == 1) then
		if  package.loaded[ lua_file_name ] then
			log.debug( "loaded has module task" )
			package.loaded[ lua_file_name ] = nil;
		else
			log.debug( "loaded has no module task" )
		end

		if  package.preload[ lua_file_name ] then
			log.debug( "preload has module task" )
			package.preload[ lua_file_name ] = nil;
		else
			log.debug( "preload has no module task" )
		end
	end
	local ns_lua_ = require( lua_file_name );
	return ns_lua_
end



--皮肤ID  头像  roleskin.csv
--ID      Head
FOO.skin_head_id_map = {
	[4] = 33,
	[3] = 30,
	[2] = 32,
	[1] = 31,

	[70] = 205,
	[71] = 206,
};


--根据 SkinID 和 Model 获得头像的图片id
FOO.get_head_id = function( SkinID_, Model_ )
	local head_id = 0
	if  SkinID_ and SkinID_>0 then
		head_id = FOO.skin_head_id_map[ SkinID_ ]
		if  not head_id then
			if  SkinID_ > 70 then
				head_id = SkinID_ + 135  --(205-70)
			else
				head_id = SkinID_ + 29   --(33-4)
			end
		end
	elseif Model_ then
		head_id = Model_
	end
	return head_id
end

-- 	| 替换为 _ 的公共函数
FOO.to_underline = function(parm)
	if  type(parm) ~= 'string' then
		return parm
	end
	return string.gsub(parm, "|", "_") or ''
end

-- 计算table下有多少个key
FOO.nkeys = function(tab)
    local ret = 0
    for idx, val in pairs(tab) do
        ret = ret + 1
    end
    return ret
end
-- local ok, nkey_tab = pcall(require, "table.nkeys")
-- if not ok then
--     nkey_tab = utils.nkeys
-- end



--检查sql安全  --不能含有分号
function FOO.check_sql( sql_ )
	if  string_find( sql_, ';' ) then
		return false
	end
	return true
end


--检查SQL字段
function  FOO.check_sql_field( in_ )
	--不能含有空格等异常符号
	if  in_ then
		local f_ = FOO.trim(in_);
		f_ = ngx.escape_uri( f_ );
		f_ = string_gsub(f_, "'", '‘');   --转换单引号
		--f_ = string_gsub(f_, '=', '〓');  --转换等号
		--f_ = string_gsub(f_, '"', '“');   --转换双引号
		--f_ = string_gsub(f_, ';', '；');  --转换分号
		return f_;
	else
		return "";
	end
end


--检查SQL字段 (ngx自带的quote模式，会强制加上单引号)
function  FOO.check_sql_field_quote( in_ )
	return ngx.quote_sql_str(in_)     ---ngx自带的mysql安全
end


--- 4294967296 * 23282 = 99995428585472
--- 4294967296 * 23282 = 99999723552768
---判断一个地图是否是家园地图
---返回值：
---1  true=是家园地图 false=非家园地图
---2  地图的作者uin
function FOO.isManorMap( p_wid_ )
	local wid_ = tonumber( p_wid_ ) or 0
	local uin_ = wid_ % 4294967296                       -- FOO.__UINT32__
	return ( wid_ == ( 99995428585472 + uin_) ), uin_    -- FOO.__UINT32__ * 23282
end


---返回自己的家园地图id
function FOO.getManorMapId( uin_ )
	return 99995428585472 + uin_    -- FOO.__UINT32__ * 23282
end


--- 只通过数值判断是否是家园地图，速度比较快
--- 4294967296 * 23282 = 99995428585472
--- 4294967296 * 23282 = 99999723552768
function FOO.isManorMapFast( p_wid_ )
	--local min_ = 99995428585472  -- FOO.__UINT32__ * 23282
	--local max_ = 99999723552768  -- FOO.__UINT32__ * 23283
	local wid_ = tonumber( p_wid_ ) or 0
	if  wid_ > 99995428585472 and wid_ < 99999723552768 then
		return true
	else
		return false
	end
end

FOO.pack_base_str = function(request_list, param_list, code_fun)
	local base_str = false
	for i=1,#param_list do
		local key	= param_list[i]
		local value = request_list[key]
		if value then
			if code_fun then
				request_list[key] = ngx.escape_uri(value)
			end

			if not base_str then
				base_str = key .. "=" .. request_list[key]
			else
				base_str = base_str .. "&" .. key .. "=" .. request_list[key]
			end
		else
			return false
		end
	end

	return base_str
end

return FOO;


